
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/mman.h>

#define MAJOR_NUM 123
#define MINOR_NUM 0

MODULE_AUTHOR("mdy");
MODULE_DESCRIPTION("mmap demo");

static struct class * hello_class;
static struct cdev hello_cdev;
static dev_t devnum = 0;
static char * modname = "hello_mod";
static char * devicename = "hello";
static char * classname = "hello_class";

static int hello_mod_mmap(struct file *, struct vm_area_struct*);
static long hello_mod_ioctl(struct file *, unsigned int, unsigned long);

struct file_operations hello_mod_fops =
{
    .owner = THIS_MODULE,
    .mmap = hello_mod_mmap,
    .unlocked_ioctl = hello_mod_ioctl
};

static long hello_mod_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
    int err = 0;
    switch(cmd)
    {
        case 0xC0506D14:
        {
            int area_len = 1<<20;
            void* kbuff = kmalloc(area_len, GFP_KERNEL);
            unsigned long phy_addr = virt_to_phys(kbuff);
            unsigned long vir_addr = 0;

            printk("hello_mod_ioctl, call vm_mmap, phy_addr = %lx\n", phy_addr);
            vir_addr = vm_mmap(pfile, 0, area_len, PROT_READ | PROT_WRITE, MAP_SHARED, phy_addr);
            printk("vm_mmap result addr = %lx\n", vir_addr);
            copy_to_user((void*)arg, &vir_addr, sizeof(unsigned long));
        }
            break;

        default:
            printk("        >in case: default\n");
            err = ENOTSUPP;
            break;
    }
    return err;
}

static int hello_mod_mmap(struct file *fp, struct vm_area_struct* vma)
{
    //vm_pgoff是vm_mmap的最后一个参数经过右移PAGE_SHIFT后的结果
    unsigned long pfn_start = vma->vm_pgoff;
    size_t size = vma->vm_end - vma->vm_start;

    //vm_start、vm_end是自动分配好的
    printk("hello_mod_mmap. start = %lx, end = %lx, pgoff = %lx\n",
        vma->vm_start, vma->vm_end, vma->vm_pgoff);

    /* Remap-pfn-range will mark the range VM_IO */
    if (remap_pfn_range(vma, vma->vm_start, pfn_start, size, vma->vm_page_prot))
    {
	    printk("remap_pfn_range fail!\n");
        return -EAGAIN;
    }

    printk("mmap success, vir_addr: 0x%lx, pfn_start: 0x%lx.\n", vma->vm_start, pfn_start);

    return 0;
}

static int __init hello_mod_init(void)
{
    int result;
    printk("+hello_mod_init()!\n");
    devnum = MKDEV(MAJOR_NUM, MINOR_NUM);
    result = register_chrdev_region(devnum, 1, modname);

    if(result < 0)
    {
        printk("hello_mod : can't get major number!\n");
        return result;
    }

    cdev_init(&hello_cdev, &hello_mod_fops);
    hello_cdev.owner = THIS_MODULE;
    hello_cdev.ops = &hello_mod_fops;
    result = cdev_add(&hello_cdev, devnum, 1);
    if(result)
        printk("Failed at cdev_add()");
    hello_class = class_create(THIS_MODULE, classname);
    if(IS_ERR(hello_class))
    {
        printk("Failed at class_create()\n");
    }
    else
    {
        device_create(hello_class, NULL, devnum,NULL, devicename);
    }

    printk("-hello_mod_init()!\n");
    return 0;
}

static void __exit hello_mod_exit(void)
{
    printk("+hello_mod_exit!\n");
    cdev_del(&hello_cdev);
    device_destroy(hello_class, devnum);
    class_destroy(hello_class);
    unregister_chrdev_region(devnum, 1);
    printk("-hello_mod_exit!\n");
    return ;
}

module_init(hello_mod_init);
module_exit(hello_mod_exit);
MODULE_LICENSE("GPL");

